﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace DCT.ViewModel
{
    #region Commands

    public interface IUndoRedoCommand
    {
        bool IsUndoChanged { get; set; }
        bool IsRedoChanged { get; set; }
        void Execute();
        void UnExecute();
    }

    public class PackageCommand : IUndoRedoCommand
    {
        public bool IsUndoChanged { get; set; }
        public bool IsRedoChanged { get; set; }
        public PackageCommand AnotherPackageCommand { get; private set; }

        public PackageCommand()
        {
            AnotherPackageCommand = new PackageCommand(this);
        }

        public PackageCommand(PackageCommand anotherPackageCommand)
        {
            AnotherPackageCommand = anotherPackageCommand;
        }

        #region ICommand Members

        public void Execute()
        {
        }

        public void UnExecute()
        {
        }

        #endregion
    }

    public class PropertyCommand : IUndoRedoCommand
    {
        private DeviceBaseViewModel _deviceViewModel;
        private DeviceMapViewModel _deviceMapViewModel;
        private string _propertyName;
        private object _newValue;
        private object _oldValue;
        public bool IsUndoChanged { get; set; }
        public bool IsRedoChanged { get; set; }

        public PropertyCommand(DeviceBaseViewModel deviceViewModel, string propertyName, object newValue, object oldValue)
        {
            _deviceViewModel = deviceViewModel;
            _deviceMapViewModel = _deviceViewModel.DeviceMapViewModel;
            _propertyName = propertyName;
            _newValue = newValue;
            _oldValue = oldValue;
            IsUndoChanged = _deviceMapViewModel.IsChanged;
            IsRedoChanged = true;
        }

        #region IUndoRedoCommand Members

        public void Execute()
        {
            _deviceMapViewModel.IsChanged = this.IsRedoChanged;
            Type t = _deviceViewModel.GetType();
            t.GetProperty(_propertyName).SetValue(_deviceViewModel, _newValue, null);
            _deviceViewModel.IsSelected = true;
        }

        public void UnExecute()
        {
            _deviceMapViewModel.IsChanged = this.IsUndoChanged;
            Type t = _deviceViewModel.GetType();
            t.GetProperty(_propertyName).SetValue(_deviceViewModel, _oldValue, null);
            _deviceViewModel.IsSelected = true;
        }

        #endregion
    }

    public class InsertCommand : IUndoRedoCommand
    {
        private DeviceBaseViewModel _deviceViewModel;
        private DeviceMapViewModel _deviceMapViewModel;
        public bool IsUndoChanged { get; set; }
        public bool IsRedoChanged { get; set; }

        public InsertCommand(DeviceBaseViewModel deviceViewModel)
        {
            _deviceViewModel = deviceViewModel;
            _deviceMapViewModel = _deviceViewModel.DeviceMapViewModel;
            IsUndoChanged = _deviceMapViewModel.IsChanged;
            IsRedoChanged = true;
        }

        #region IUndoRedoCommand Members

        public void Execute()
        {
            _deviceMapViewModel.IsChanged = this.IsRedoChanged;
            _deviceMapViewModel.DeviceRepository.Add(_deviceViewModel);
            _deviceViewModel.IsSelected = true;
        }

        public void UnExecute()
        {
            _deviceMapViewModel.IsChanged = this.IsUndoChanged;
            _deviceMapViewModel.DeviceRepository.Remove(_deviceViewModel);

        }

        #endregion
    }

    public class DeleteCommand : IUndoRedoCommand
    {
        private DeviceBaseViewModel _deviceViewModel;
        private DeviceMapViewModel _deviceMapViewModel;
        public bool IsUndoChanged { get; set; }
        public bool IsRedoChanged { get; set; }

        public DeleteCommand(DeviceBaseViewModel deviceViewModel)
        {
            _deviceViewModel = deviceViewModel;
            _deviceMapViewModel = _deviceViewModel.DeviceMapViewModel;
            IsUndoChanged = _deviceMapViewModel.IsChanged;
            IsRedoChanged = true;
        }

        #region IUndoRedoCommand Members

        public void Execute()
        {
            _deviceMapViewModel.IsChanged = this.IsRedoChanged;
            _deviceMapViewModel.DeviceRepository.Remove(_deviceViewModel);
        }

        public void UnExecute()
        {
            _deviceMapViewModel.IsChanged = this.IsUndoChanged;
            _deviceMapViewModel.DeviceRepository.Add(_deviceViewModel);
            _deviceViewModel.IsSelected = true;
        }

        #endregion
    }

    #endregion

    #region UndoRedo

    public class UndoRedo
    {
        #region Fields

        private Stack<IUndoRedoCommand> _undoCommands = new Stack<IUndoRedoCommand>();
        private Stack<IUndoRedoCommand> _redoCommands = new Stack<IUndoRedoCommand>();
        private bool _isInsertEnable = true;

        #endregion

        public bool IsInsertEnable
        {
            get
            {
                return _isInsertEnable;
            }
            set
            {
                _isInsertEnable = value;
            }
        }

        #region Event

        public event EventHandler EnableDisableUndoRedoFeature;

        #endregion

        #region Methods

        public void Redo(int levels)
        {
            _isInsertEnable = false;

            for (int i = 1; i <= levels; i++)
            {
                if (_redoCommands.Count != 0)
                {
                    IUndoRedoCommand command = _redoCommands.Pop();

                    if (command is PackageCommand)
                    {
                        PackageCommand anotherPackageCommand = (command as PackageCommand).AnotherPackageCommand;
                        if (_redoCommands.Contains(anotherPackageCommand))
                        {
                            _undoCommands.Push(command);

                            command = _redoCommands.Pop();
                            while (!(command == anotherPackageCommand))
                            {
                                command.Execute();
                                _undoCommands.Push(command);
                                command = _redoCommands.Pop();
                            }

                            _undoCommands.Push(command);
                        }
                        else
                        {
                            this.Redo(1);
                        }
                    }
                    else
                    {
                        command.Execute();
                        _undoCommands.Push(command);
                    }
                }
            }

            if (EnableDisableUndoRedoFeature != null)
            {
                EnableDisableUndoRedoFeature(null, null);
            }

            _isInsertEnable = true;
        }

        public void PopRedoStack(int levels)
        {
            for (int i = 1; i <= levels; i++)
            {
                if (_redoCommands.Count != 0)
                {
                    IUndoRedoCommand command = _redoCommands.Pop();

                    if (command is PackageCommand)
                    {
                        PackageCommand anotherPackageCommand = (command as PackageCommand).AnotherPackageCommand;
                        if (_redoCommands.Contains(anotherPackageCommand))
                        {
                            command = _redoCommands.Pop();
                            while (!(command == anotherPackageCommand))
                            {
                                command = _redoCommands.Pop();
                            }
                        }
                        else
                        {
                            this.PopRedoStack(1);
                        }
                    }
                }
            }

            if (EnableDisableUndoRedoFeature != null)
            {
                EnableDisableUndoRedoFeature(null, null);
            }
        }

        public void Undo(int levels)
        {
            _isInsertEnable = false;

            for (int i = 1; i <= levels; i++)
            {
                if (_undoCommands.Count != 0)
                {
                    IUndoRedoCommand command = _undoCommands.Pop();

                    if (command is PackageCommand)
                    {
                        PackageCommand anotherPackageCommand = (command as PackageCommand).AnotherPackageCommand;
                        if (_undoCommands.Contains(anotherPackageCommand))
                        {
                            _redoCommands.Push(command);

                            command = _undoCommands.Pop();
                            while (!(command == anotherPackageCommand))
                            {
                                command.UnExecute();
                                _redoCommands.Push(command);
                                command = _undoCommands.Pop();
                            }

                            _redoCommands.Push(command);
                        }
                        else
                        {
                            this.Undo(1);
                        }
                    }
                    else
                    {
                        command.UnExecute();
                        _redoCommands.Push(command);
                    }
                }
            }

            if (EnableDisableUndoRedoFeature != null)
            {
                EnableDisableUndoRedoFeature(null, null);
            }

            _isInsertEnable = true;
        }

        public void PopUndoStack(int levels)
        {
            for (int i = 1; i <= levels; i++)
            {
                if (_undoCommands.Count != 0)
                {
                    IUndoRedoCommand command = _undoCommands.Pop();

                    if (command is PackageCommand)
                    {
                        PackageCommand anotherPackageCommand = (command as PackageCommand).AnotherPackageCommand;
                        if (_undoCommands.Contains(anotherPackageCommand))
                        {
                            command = _undoCommands.Pop();
                            while (!(command == anotherPackageCommand))
                            {
                                command = _undoCommands.Pop();
                            }
                        }
                        else
                        {
                            this.PopUndoStack(1);
                        }
                    }
                }
            }

            if (EnableDisableUndoRedoFeature != null)
            {
                EnableDisableUndoRedoFeature(null, null);
            }
        }

        #region UndoHelperFunctions

        public void InsertInUnDoRedoForPackage(PackageCommand cmd)
        {
            if (_isInsertEnable)
            {
                _undoCommands.Push(cmd);
                _redoCommands.Clear();
                if (EnableDisableUndoRedoFeature != null)
                {
                    EnableDisableUndoRedoFeature(null, null);
                }
                //(Application.Current.MainWindow as MainWindow).IsChanged = true;
            }
        }

        public void InsertInUnDoRedoForInsert(DeviceBaseViewModel deviceViewModel)
        {
            if (_isInsertEnable)
            {
                IUndoRedoCommand cmd = new InsertCommand(deviceViewModel);
                _undoCommands.Push(cmd);
                _redoCommands.Clear();
                if (EnableDisableUndoRedoFeature != null)
                {
                    EnableDisableUndoRedoFeature(null, null);
                }
                deviceViewModel.DeviceMapViewModel.IsChanged = true;
            }
        }

        public void InsertInUnDoRedoForDelete(DeviceBaseViewModel deviceViewModel)
        {
            if (_isInsertEnable)
            {
                IUndoRedoCommand cmd = new DeleteCommand(deviceViewModel);
                _undoCommands.Push(cmd);
                _redoCommands.Clear();
                if (EnableDisableUndoRedoFeature != null)
                {
                    EnableDisableUndoRedoFeature(null, null);
                }
                deviceViewModel.DeviceMapViewModel.IsChanged = true;
            }
        }

        public void InsertInUnDoRedoForProperty(DeviceBaseViewModel deviceViewModel, string propertyName, object newValue, object oldValue)
        {
            if (_isInsertEnable)
            {
                IUndoRedoCommand cmd = new PropertyCommand(deviceViewModel, propertyName, newValue, oldValue);
                _undoCommands.Push(cmd);
                _redoCommands.Clear();
                if (EnableDisableUndoRedoFeature != null)
                {
                    EnableDisableUndoRedoFeature(null, null);
                }
                deviceViewModel.DeviceMapViewModel.IsChanged = true;
            }
        }

        #endregion

        public bool IsUndoPossible()
        {
            return _undoCommands.Count != 0;
        }

        public bool IsRedoPossible()
        {
            return _redoCommands.Count != 0;
        }

        public void Clear()
        {
            _undoCommands.Clear();
            _redoCommands.Clear();
            if (EnableDisableUndoRedoFeature != null)
            {
                EnableDisableUndoRedoFeature(null, null);
            }
        }

        public void SetChangeState()
        {
            foreach (IUndoRedoCommand com in _undoCommands)
            {
                com.IsRedoChanged = true;
                com.IsUndoChanged = true;
            }

            foreach (IUndoRedoCommand com in _redoCommands)
            {
                com.IsRedoChanged = true;
                com.IsUndoChanged = true;
            }

            IUndoRedoCommand undocom;
            try
            {
                undocom = _undoCommands.FirstOrDefault(v => !(v is PackageCommand));
            }
            catch
            {
                undocom = null;
            }
            if (undocom != null)
            {
                undocom.IsRedoChanged = false;
            }

            IUndoRedoCommand redocom;
            try
            {
                redocom = _redoCommands.FirstOrDefault(v => !(v is PackageCommand));
            }
            catch
            {
                redocom = null;
            }
            if (redocom != null)
            {
                redocom.IsUndoChanged = false;
            }
        }

        #endregion
    }

    #endregion
}